machines = { 
# Machine    : ["buildarch", "platform", "architecture bits", "cores", "tempdir" ]
"sx104"      : ["linux24-x86",    "Linux",   32, 4, "tmp" ],
"sx035rhel3" : ["linux24-x86_64", "Linux",   64, 1, "/tmp" ],
"sx035sle9"  : ["linux26-x86_64", "Linux",   64, 1, "/tmp" ],
"sw103"      : ["em64t-win",      "Windows", 64, 1, "e:/temp" ],
"sw091"      : ["win32",          "Windows", 32, 1, "e:/temp" ],
"wx030"      : ["linux24-em64t",  "Linux",   64, 2, "/tmp" ], # Raymond
"wx037"      : ["linux-x86_64",   "Linux",   64, 1, "tmp" ], # Rene
"wx051"      : ["linux24-em64t",  "Linux",   64, 1, "tmp" ],
"ubuntu32"   : ["linux26-x86",    "Linux",   32, 1, "tmp" ], # vbox linux26-x86 test
"wx044"      : ["linux26-x86_64", "Linux",   64, 4, "tmp" ], # Lo
"wx046"      : ["linux24-em64t",  "Linux",   64, 1, "tmp" ], # Neel
"rene-vbox"  : ["linux26-x86_64", "Linux",   64, 1, "tmp" ], # vbox linux26-x86_64 test
"dw036"      : ["win32",          "Windows", 32, 1, "tmp" ], # Neel
"wx018"      : ["linux26-x86_64", "Linux",   64, 4, "tmp" ],
"win32_local": ["win32_vs9sp1",   "Windows", 32, 1, "c:/tmp" ], #generic local
"win64_local" : ["win64_vs9sp1",  "Windows", 64, 1, "c:/tmp" ], #generic local
#laptops
"NW032" : ["win64_vs9sp1",  "Windows", 64, 1, "c:/tmp" ], #generic local
# Solver machines
"wtma81" : ["hp1100_ia64",  "Linux", 64, 1, "tmp" ], 
"sx026" : ["aix_64",  "Linux", 64, 2, "tmp" ],

           }
#todo: we can use the platform module to find out the machine architecture
#      and the operating system

# Cannot find all information that's needed on Windows. 

import os, urllib, sys, shutil
import platform as pf

class Machine:
  def __init__(self, buildarch = None, platform = None, bits = None, cores = None):

    self.bits = bits
    if self.bits is None:
      self.bits = int(pf.architecture()[0][0:2])

    self.platform = platform
    if self.platform is None:
      self.platform = pf.system()

    self.buildarch = buildarch
    if self.buildarch is None:
      if os.environ.has_key('VisualStudioVersion'):
        print "Visual Studio Version: ", os.environ['VisualStudioVersion']
        if os.environ['VisualStudioVersion'] == "11.0":
          self.buildarch = 'win' + str(self.bits) + '_vs11'
    if self.buildarch is None:
      self.buildarch = self.platform + str(self.bits)

    self.cores = cores
    if self.cores is None:
      self.cores = 1

  def isWindows(self):
    return self.platform == "Windows"

  def isLinux(self):
    return self.platform == "Linux"
 
  def isVS2008(self):
    #todo fix: remove this function and add isVisualStudio and getVisualStudioVersion
    return True

  def print_info(self):
    print "--------------------------------------------------------------------------"
    print "platform:    ", self.platform
    print "bits:        ", self.bits 
    print "buildarch:   ", self.buildarch     
    print "cores:       ", self.cores
    print "--------------------------------------------------------------------------"



class Directories:
  def __init__(self, machine, tempdir = "tmp", downloaddir="downloads", envirodir="enviro"):
    tmp = tempdir + "/" 

    self.temp = os.path.abspath( tempdir )
    self._createdir( self.temp )

    self.build = os.path.abspath(self.temp + os.sep + machine.buildarch )
    self._createdir( self.build ) 

    self.enviro = os.path.abspath(envirodir + os.sep + machine.buildarch )
    self._createdir( self.enviro ) 

    self.download = os.path.abspath(downloaddir)
    self._createdir( self.download ) 

    self.workingdir = os.getcwd()

  def _createdir( self, dirname ):
    if not os.path.exists( dirname ):
      os.makedirs( dirname )

  def __repr__(self):
    return "Download: " + self.download + "\n" \
           + "Build:    " + self.build + "\n" \
           + "Enviro:   " + self.enviro

  def print_info(self):
    print "--------------------------------------------------------------------------"
    print "temp:           ", self.temp
    print "build:          ", self.build 
    print "enviro:         ", self.enviro     
    print "download:       ", self.download
    print "--------------------------------------------------------------------------"


class Configuration(object):

  LINEBUFFERSIZE = 200  # Show this many lines when an error occurs.

  def __init__(self, machine, directories, verbose, rebuilds, dependencies, continueonerrors, showsummary):
    self.machine = machine
    self.directories = directories
    self.verbose = verbose
    self.rebuilds = rebuilds
    self.builddependencies = dependencies
    self.continueonerrors = continueonerrors
    self.showsummary = showsummary


class Downloader:
  def __init__(self):
    pass

  def download( self, url, target ):
    if os.path.exists( target ):
      print( "Package available:  " +  target )
    else:
      if url.startswith("file://"):
        filename = sys.path[0] + os.sep + url[7:]
        if os.path.exists( filename ):
          shutil.copy2(filename, target )
        else:
          raise Exception("Cannot find file: %s" % filename)
      else:
        code = urllib.urlopen(url).getcode()
        if code is not None and code != 200:
          raise Exception("Download URL: %s failed with code %s" % (url,code))
        try:
          length = urllib.urlopen(url).info().getheader("Content-Length")
          if length:
            length = int(length)
          else:
            length = 1
        except:
          length = 1

        def reporthook(*a):
          if a[0] % 8 == 0:
           downloaded = a[0] * a[1]
           print "[ %s%% ] %s KB" % ( int(float(downloaded) / length * 100), downloaded / 1024) 
           sys.stdout.flush()

        print( "Downloading:" )
        print( url, "->", target )
        print "Total bytes to download: " + str(length)
        temptarget = target + ".temp"
        urllib.urlretrieve(url, temptarget, reporthook)
        print ""  # newline
        shutil.move( temptarget, target )



class FetchSource(object):
  def __init__(self):
    pass

class DownloadExtract(FetchSource):
  def __init__(self):
    FetchSource.__init__(self)

  def fetch(self, url, downloadlocation, buildlocation ):
    Downloader().download( url, downloadlocation)
    return Extracter().extract( downloadlocation, buildlocation)


class Collector:
  """Collects the files and folders of several directories into one directory
     Useful for creating a single (3rdparty) dir with lib, bin and include
  """
  def __init__(self, basedirs, destination, removeandcreatedir = False):

    if removeandcreatedir:
      #create the actual dir
      if os.path.exists(destination):
        print "Removing: ", destination
        shutil.rmtree( destination )
      os.makedirs(destination)
  
    #copy the files in include and lib dirs
    for basedir in basedirs:
      for dirname in os.listdir(basedir):
        if os.path.isdir( basedir + os.sep + dirname ):
          if not os.path.exists(destination+os.sep+dirname):
            os.makedirs(destination+os.sep+dirname)
            print( "creating: " + destination+os.sep+dirname )
          for fileordir in os.listdir(basedir+os.sep+dirname):
            curname = os.path.join(basedir,dirname,fileordir)
            if os.path.isfile(curname):
              print( "copy file: " + curname + " to " + destination+os.sep+dirname+os.sep )
              shutil.copy2(curname, destination+os.sep+dirname+os.sep)


      code = urllib.urlopen(url).getcode()
      if code != 200:
        raise Exception("Download URL: %s failed with code %s" % (url,code))

      try:
        length = urllib.urlopen(url).info().getheader("Content-Length")
        if length:
          length = int(length)
        else:
          length = 1
      except:
        length = 1

      def reporthook(*a):
        if a[0] % 8 == 0:
         downloaded = a[0] * a[1]
         print "[ %s%% ] %s KB" % ( int(float(downloaded) / length * 100), downloaded / 1024) 
         sys.stdout.flush()

      print( "Downloading:" )
      print( url, "->", target )
      print "Total bytes to download: " + str(length)
      temptarget = target + ".temp"
      urllib.urlretrieve(url, temptarget, reporthook)
      print ""  # newline
      shutil.move( temptarget, target )



class FetchSource(object):
  def __init__(self):
    self.rev = None
    pass

  def checkoutRevision(self, rev):
    self.rev = rev

class DownloadExtract(FetchSource):
  def __init__(self):
    FetchSource.__init__(self)

  def fetch(self, url, downloadlocation, buildlocation ):
    Downloader().download( url, downloadlocation)
    return Extracter().extract( downloadlocation, buildlocation)

class FetchSourceSVN(FetchSource):
  def __init__(self, mode="update", executable="svn"):
    """ mode: update is the only mode that's supported"""
    FetchSource.__init__(self)
    self.executable = executable
    self.mode = mode

  def fetch(self, url, downloadlocation, buildlocation ):
    targetdir = downloadlocation.split(os.sep)[-1]
    targetpath = buildlocation + os.sep  + targetdir
    
    if self.rev is not None:
      revparam = "-r %s" % self.rev
    else:
      revparam = ""

    if os.path.exists( targetpath ):
      # SVN update
      os.system( self.executable + " update " + targetpath + " " + revparam )
    else:
      # SVN checkout
      os.system( self.executable + " checkout " + url + " " + targetpath + " " + revparam )

    return targetpath


class FetchSourceHG(FetchSource):
  def __init__(self, mode="update", executable="hg"):
    """ mode: update is the only mode that's supported"""
    FetchSource.__init__(self)
    self.executable = executable
    self.mode = mode

  def fetch(self, url, downloadlocation, buildlocation ):
    targetdir = downloadlocation.split(os.sep)[-1]
    targetpath = buildlocation + os.sep  + targetdir

    if self.rev is not None:
      revparam = "-r %s" % self.rev
    else:
      revparam = ""

    if os.path.exists( targetpath ):
      # hg update
      os.chdir( targetpath )
      os.system( " ".join([self.executable, "pull", url]) )
      os.system( " ".join([self.executable, "up", revparam]) )
    else:
      # SVN checkout
      os.system( " ".join([self.executable, "clone", revparam, url, targetpath]) )

    return targetpath


class Collector:
  """Collects the files and folders of several directories into one directory
     Useful for creating a single (3rdparty) dir with lib, bin and include
  """
  def __init__(self, basedirs, destination, removeandcreatedir = False):

    if removeandcreatedir:
      #create the actual dir
      if os.path.exists(destination):
        print "Removing: ", destination
        shutil.rmtree( destination )
      os.makedirs(destination)
  
    #copy the files in include and lib dirs
    for basedir in basedirs:
      for dirname in os.listdir(basedir):
        if os.path.isdir( basedir + os.sep + dirname ):
          if not os.path.exists(destination+os.sep+dirname):
            os.makedirs(destination+os.sep+dirname)
            print( "creating: " + destination+os.sep+dirname )
          for fileordir in os.listdir(basedir+os.sep+dirname):
            curname = os.path.join(basedir,dirname,fileordir)
            if os.path.isfile(curname):
              print( "copy file: " + curname + " to " + destination+os.sep+dirname+os.sep )
              shutil.copy2(curname, destination+os.sep+dirname+os.sep)
            elif not (curname.split(os.sep)[-1] in ("man", "pkgconfig")) : #skip man dir and pkgconfig
              print( "copy dir: " + curname + " to " + destination+os.sep+dirname+os.sep )
              shutil.copytree(curname, destination+os.sep+dirname+os.sep+fileordir)
            else:
              print "skipping: ", curname

class Extracter:
  def __init__(self):
    pass

  def extract( self, archivepath, builddir ):
    if archivepath.endswith(".jar"):
      tgt = os.path.join( builddir, os.path.splitext(os.path.basename(archivepath) )[0] ) # builddir + jarname without extension
      try: 
        os.makedirs( tgt )
      except: pass
      shutil.copy( archivepath, tgt )
      return  tgt
    if (archivepath.endswith(".zip")):
      return self.unzip( archivepath, builddir )
    else:
      return self.untar( archivepath, builddir )


  def is_extracted( self, extracted_dir ):
    """ returns true if the extracted file is available  """
    is_extracted = os.path.exists( extracted_dir )
    if is_extracted:
       print "Extracted version available: ", extracted_dir
    return is_extracted

  def untar( self, packed, builddir ):
    """ untar a packed filename to extracted filename
        for the extracted location we use the name of the first members of the archive (folder name)
    """
    import tarfile

    if packed.endswith('bz2'):
      tf = tarfile.open(packed, 'r:bz2')
    else:
      try:
        import gunzip
        tf = tarfile.open(packed, 'r:gz')
      except ImportError:
        try:
          print "Retry tarfile open", packed
          tf = tarfile.open(packed) 
        except:
          print "Using gunzip program installed on the OS"
          unpacked = packed[:-3]
          if packed.endswith('tgz'):
            unpacked += 'tar'
          if not os.path.exists( unpacked ):
            shutil.copy( packed, packed + ".tmp" )
            os.system( "gunzip " + packed )
            shutil.move( packed + ".tmp", packed )
          packed = unpacked
          tf = tarfile.open(packed, 'r')

    extracted_dir = builddir + os.sep + tf.getmembers()[0].name.split('/')[0]

    if not self.is_extracted( extracted_dir ):
      print "Extracting: "
      print packed, "->", extracted_dir
      tf.extractall( builddir )
    tf.close()

    return extracted_dir


  def unzip( self, packed, builddir ):
    """ unzip a packed filename to extracted filename"""

    self.output_dir = builddir

    import zipfile
    self.input = packed

    try:
      zp = zipfile.ZipFile(self.input)
    except zipfile.BadZipfile:

      # This code removes the comments from the end of the zip file. 
      # 
      # Code snippit from:
      # http://groups.google.com/group/comp.lang.python/msg/c2008e48368c6543
      import cStringIO
      original = open(self.input, 'rb') 
      try: 
        data = original.read() 
      finally: 
        original.close() 
      ignoreable = 4500
      position = data.rindex(zipfile.stringEndArchive, 
                             -(22 + ignoreable), -20) 
      coredata = cStringIO.StringIO(data[: 22 + position]) 
      zp = zipfile.ZipFile(coredata) 


    firstname = zp.namelist()[0].split("/")[0]
    directorycreated = zp.namelist()[0].find("/") > -1 and all([ firstname == x.split("/")[0] for x in zp.namelist() ]) 

    if directorycreated:
      self.extracted_dir = self.output_dir + os.sep + firstname
    else:
      self.extracted_dir = self.output_dir + os.sep + self.input.split(os.sep)[-1]
      if self.extracted_dir.endswith(".zip"):
        self.extracted_dir = self.extracted_dir[:-4]

    print "extracting to:", self.extracted_dir

    if not self.is_extracted( self.extracted_dir ):
      print "Extracting: "
      print  self.input, "->", self.extracted_dir      
      if sys.hexversion >= 0x2060000:   #python version 2.6 needed      
        if directorycreated:
          self.extractall_with_symlinks( zp, self.output_dir )
        else:
          self.extractall_with_symlinks( zp, self.extracted_dir )
      else:
        if directorycreated:
          os.system( "unzip " + self.input + " -d " + self.output_dir )
        else:
          os.system( "unzip " + self.input + " -d " + self.extracted_dir )

    return self.extracted_dir

  def extractall_with_symlinks( self, zopen,  outputdir ):
    for f in zopen.namelist():
      info = zopen.getinfo(f)
      if f.endswith('/'):
        try:
          os.makedirs(os.path.join(outputdir, f))
        except:
          pass
      elif info.external_attr == 2717843456L:
        target = zopen.read(f)
        os.symlink(target, os.path.join(outputdir, f) )
      else:
        zopen.extract( f, outputdir )

  def target_dir( self ):
    return self.extracted_dir


class Url(object):
  def __init__(self, url ):
    self.url = url

  def packagearchive( self ):
    pos = self.url.rfind('/')
    return self.url[pos+1:]

  def packagedir( self ):
    return self._extensionless(self.packagearchive() )

  def _extensionless( self, pkg ):
    # TODO
    if pkg.endswith( ".tar.gz" ):
      return  pkg[:-7]
    if pkg.endswith( ".tar.bz2" ):
      return  pkg[:-8]
    return  os.path.splitext( pkg )[0]




class Tools(object):
  def __init__(self, machine, dirs ):
    self.dirs = dirs
    self.machine = machine
     
  def execute( self, command ):
    print command
    if os.system( command ) != 0:
      raise Exception("Execution failed on command: " + command )



